package org.abc.fund.service.funds;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.abc.fund.entity.funds.FundBasicInfo;
import org.abc.fund.entity.funds.FundTag;
import org.abc.fund.entity.funds.FundTagRelation;
import org.abc.fund.repository.funds.FundBasicInfoRepository;
import org.abc.fund.repository.funds.FundTagRelationRepository;
import org.abc.fund.repository.funds.FundTagRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.time.Instant;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 基金自动打标签：根据基金固有信息（类型、基金类型、风格）生成标签及关系
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class FundAutoTaggingService {

    private static final String CATEGORY_FUND_TYPE = "基金类型";
    private static final String CATEGORY_PRODUCT_TYPE = "产品类型";
    private static final String CATEGORY_STYLE = "风格";
    private static final String CATEGORY_OPERATE_MODE = "运作方式";
    private static final String CATEGORY_UNDERLYING_ASSET = "标的资产";
    private static final String CATEGORY_MARKET_STATUS = "上市状态";
    private static final String CATEGORY_THEME = "主题";
    private static final String CATEGORY_INDUSTRY = "行业";
    private static final String CATEGORY_STRATEGY = "策略";

    private static final List<NameKeywordRule> NAME_KEYWORD_RULES = List.of(
            new NameKeywordRule("汽车", "汽车", CATEGORY_INDUSTRY),
            new NameKeywordRule("新能源", "新能源", CATEGORY_THEME),
            new NameKeywordRule("电信", "电信", CATEGORY_INDUSTRY),
            new NameKeywordRule("消费", "消费", CATEGORY_THEME),
            new NameKeywordRule("医药", "医药", CATEGORY_INDUSTRY),
            new NameKeywordRule("医疗", "医疗", CATEGORY_INDUSTRY),
            new NameKeywordRule("生物医药", "生物医药", CATEGORY_THEME),
            new NameKeywordRule("科技", "科技", CATEGORY_THEME),
            new NameKeywordRule("港股通", "港股通", CATEGORY_THEME),
            new NameKeywordRule("恒生", "恒生", CATEGORY_THEME),
            new NameKeywordRule("中证500", "中证500", CATEGORY_THEME),
            new NameKeywordRule("纳斯达克100", "纳斯达克100", CATEGORY_THEME),
            new NameKeywordRule("纳斯达克", "纳斯达克", CATEGORY_THEME),
            new NameKeywordRule("纳指", "纳指", CATEGORY_THEME),
            new NameKeywordRule("品质", "品质", CATEGORY_STRATEGY),
            new NameKeywordRule("可持续", "可持续", CATEGORY_STRATEGY),
            new NameKeywordRule("保证金", "保证金", CATEGORY_PRODUCT_TYPE),
            new NameKeywordRule("交易型货币", "交易型货币", CATEGORY_PRODUCT_TYPE),
            new NameKeywordRule("债券", "债券", CATEGORY_FUND_TYPE),
            new NameKeywordRule("纯债", "纯债", CATEGORY_STRATEGY),
            new NameKeywordRule("可转债", "可转债", CATEGORY_STRATEGY),
            new NameKeywordRule("债基", "债券型", CATEGORY_FUND_TYPE),
            new NameKeywordRule("ETF", "ETF", CATEGORY_PRODUCT_TYPE, true),
            new NameKeywordRule("LOF", "LOF", CATEGORY_PRODUCT_TYPE, true),
            new NameKeywordRule("QDII", "QDII", CATEGORY_PRODUCT_TYPE, true),
            new NameKeywordRule("联接", "联接基金", CATEGORY_PRODUCT_TYPE),
            new NameKeywordRule("指数", "指数", CATEGORY_THEME),
            new NameKeywordRule("货币", "货币型", CATEGORY_FUND_TYPE),
            new NameKeywordRule("成长", "成长", CATEGORY_STYLE),
            new NameKeywordRule("价值", "价值", CATEGORY_STYLE)
    );

    private final FundBasicInfoRepository fundBasicInfoRepository;
    private final FundTagRepository fundTagRepository;
    private final FundTagRelationRepository fundTagRelationRepository;

    private record TagCandidate(String name, String category) {
    }

    private record NameKeywordRule(String keyword, String normalizedName, String category, boolean ignoreCase) {
        private NameKeywordRule(String keyword, String normalizedName, String category) {
            this(keyword, normalizedName, category, false);
        }

        boolean matches(String normalized, String normalizedUpper) {
            if (!StringUtils.hasText(keyword)) {
                return false;
            }
            return ignoreCase ? normalizedUpper.contains(keyword.toUpperCase(Locale.ROOT)) : normalized.contains(keyword);
        }
    }

    @Transactional
    public AutoTaggingResult autoAssignTags() {
        List<FundBasicInfo> funds = fundBasicInfoRepository.findAll();
        if (funds.isEmpty()) {
            return new AutoTaggingResult(0, 0, 0, Instant.now());
        }

        AtomicInteger createdTags = new AtomicInteger();
        AtomicInteger createdRelations = new AtomicInteger();
        Map<String, FundTag> tagCache = new ConcurrentHashMap<>();

        funds.forEach(fund -> {
            Set<TagCandidate> candidates = deriveCandidates(fund);
            fundTagRelationRepository.deleteByFundCode(fund.getCode());
            if (candidates.isEmpty()) {
                return;
            }
            for (TagCandidate candidate : candidates) {
                FundTag tag = findOrCreateTag(candidate, tagCache, createdTags);
                if (tag != null && !fundTagRelationRepository.existsByFundCodeAndTag_Id(fund.getCode(), tag.getId())) {
                    FundTagRelation relation = new FundTagRelation();
                    relation.setFundCode(fund.getCode());
                    relation.setTag(tag);
                    fundTagRelationRepository.save(relation);
                    createdRelations.incrementAndGet();
                }
            }
        });

        AutoTaggingResult result = new AutoTaggingResult(
                funds.size(),
                createdTags.get(),
                createdRelations.get(),
                Instant.now()
        );
        log.info("自动标签生成完成：处理基金 {} 只，新建标签 {} 个，新建关系 {} 条",
                result.processedFunds(), result.newTags(), result.newRelations());
        return result;
    }

    private Set<TagCandidate> deriveCandidates(FundBasicInfo fund) {
        Set<TagCandidate> candidates = new LinkedHashSet<>();
        addCandidate(candidates, fund.getType(), CATEGORY_PRODUCT_TYPE);
        addCandidate(candidates, fund.getFundType(), CATEGORY_FUND_TYPE);
        addCandidate(candidates, fund.getInvestStyle(), CATEGORY_STYLE);
        addCandidate(candidates, fund.getOperateMode(), CATEGORY_OPERATE_MODE);
        addCandidate(candidates, fund.getUnderlyingAssetType(), CATEGORY_UNDERLYING_ASSET);
        addCandidate(candidates, fund.getStatus(), CATEGORY_MARKET_STATUS);
        addNameKeywordCandidates(fund.getName(), candidates);
        return candidates;
    }

    private void addCandidate(Set<TagCandidate> candidates, String value, String category) {
        if (!StringUtils.hasText(value)) {
            return;
        }
        String normalized = value.trim();
        if (!normalized.isEmpty()) {
            candidates.add(new TagCandidate(normalized, category));
        }
    }

    private void addNameKeywordCandidates(String name, Set<TagCandidate> candidates) {
        if (!StringUtils.hasText(name)) {
            return;
        }
        String normalized = name.replaceAll("[()（）\\s]+", "");
        String normalizedUpper = normalized.toUpperCase(Locale.ROOT);
        for (NameKeywordRule rule : NAME_KEYWORD_RULES) {
            if (rule.matches(normalized, normalizedUpper)) {
                candidates.add(new TagCandidate(rule.normalizedName(), rule.category()));
            }
        }
    }

    private FundTag findOrCreateTag(TagCandidate candidate, Map<String, FundTag> cache, AtomicInteger createdTags) {
        String cacheKey = candidate.category() + "::" + candidate.name();
        FundTag cached = cache.get(cacheKey);
        if (cached != null) {
            return cached;
        }

        List<FundTag> existing = fundTagRepository.findByNameAndCategory(candidate.name(), candidate.category());
        FundTag tag = existing.isEmpty() ? null : existing.get(0);
        if (tag == null) {
            tag = new FundTag();
            tag.setName(candidate.name());
            tag.setCategory(candidate.category());
            tag = fundTagRepository.save(tag);
            createdTags.incrementAndGet();
        }
        cache.put(cacheKey, tag);
        return tag;
    }

    public record AutoTaggingResult(int processedFunds, int newTags, int newRelations, Instant executedAt) {
    }
}


